﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Script.Serialization;

using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Utilities;
using Newtonsoft.Json;
using Drempelvrij.Models.Routes;
using Drempelvrij.DAL.UnitOfWork;
using Drempelvrij.DAL.Repositories;
using Drempelvrij.Models.Entities;
using System.Drawing;
using System.Web.UI;
using Drempelvrij.Models.JSON;
using Drempelvrij.UI.Web.ViewModels;

namespace Drempelvrij.UI.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IUnitOfWork _uow;
        private readonly IBestratingRepository _bestratingRepository;
        private readonly IGeoDataRepository _geoDataRepository;
        private readonly IParkeerPlaatsRepository _parkeerPlaatsRepository;

        public HomeController(IUnitOfWork uow, IBestratingRepository bestratingRepository, IGeoDataRepository geoDataRepository, IParkeerPlaatsRepository parkeerPlaatsRepository)
        {
            _uow = uow;
            _bestratingRepository = bestratingRepository;
            _geoDataRepository = geoDataRepository;
            _parkeerPlaatsRepository = parkeerPlaatsRepository;
        }

        //
        // GET: /Calc/

        public ActionResult Index()
        {
            return View();
        }

        /// <summary>
        /// Toon de route voor de gebruiker inclusief de parkeerplaatsen.
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public ActionResult ShowRoute()
        {
            Wrapper wrapper = new Wrapper();
            wrapper.van = (string)Session["van"];
            wrapper.naar = (string)Session["naar"];

            List<CompleteDirectionsWrapper> list = (List<CompleteDirectionsWrapper>)Session["routeList"];
            wrapper.listCompleteDW = list;

            wrapper.vanSerialized = "\"" + wrapper.van + "\"";
            wrapper.naarSerialized = "\"" + wrapper.naar + "\"";

            return View(wrapper);
        }

        /// <summary>
        /// Ontvangt de route gegevens van de post en berekent de gehele route + metadata. Stuurt de gebruiker daarna naar de ShowRoute pagina om de route te tonen.
        /// </summary>
        /// <param name="col"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult postRoute(HomeViewModel col)
        {
            string van = col.from;
            string naar = col.destination;

            RouteWrapper routeWrapper = getRouteDirectionsModel(van, naar);
            List<CompleteDirectionsWrapper> list = null;

            while (list == null)
            {
                list = getCompleteDirectionsModel(routeWrapper);
            }

            Session.Add("routeList", list);
            Session.Add("van", van);
            Session.Add("naar", naar);

            return Redirect("ShowRoute");
        }

        /// <summary>
        /// Returned een JsonResult om de metadata opnieuw op te halen voor de desbetreffende route.
        /// </summary>
        /// <param name="routeid"></param>
        /// <returns>Returned een JsonResult met de desbetreffende meta data.</returns>
        [HttpPost]
        public JsonResult postAjax(int routeid)
        {
            List<CompleteDirectionsWrapper> list = (List<CompleteDirectionsWrapper>)Session["routeList"];

            CompleteDirectionsWrapper cdw = list.ElementAt(routeid);

            AjaxMetaRoute ar = new AjaxMetaRoute();

            ar.gemiddeldeWaarde = cdw.gemiddeldeBestratingWaarde;
            ar.routeid = cdw.id;
            ar.soortenbestrating = cdw.getSoortenBestratingInRoute();
            ar.streetNotFoundInDB = cdw.getStraatZonderBestratingGegevensInDBVoorgekomen();

            return Json(ar);
        }

        /// <summary>
        /// Haalt de index value op van de beste route voor rolstoelgebruikers.
        /// </summary>
        /// <returns>Een JsonResult met daarin een int index voor de beste route.</returns>
        public JsonResult postAjaxHighestNumber()
        {
            List<CompleteDirectionsWrapper> list = (List<CompleteDirectionsWrapper>)Session["routeList"];
            decimal index = list.FirstOrDefault().id - 1;
            decimal lowest = list.FirstOrDefault().gemiddeldeBestratingWaarde;

            foreach (CompleteDirectionsWrapper item in list)
            {
                if (item.gemiddeldeBestratingWaarde < lowest)
                {
                    lowest = item.gemiddeldeBestratingWaarde;
                    index = item.id - 1;
                }
            }

            return Json(index);
        }

        /********************************************************************************/

        /// <summary>
        /// Aan de hand van een lat en lng wordt een straat gereturned. Deze roept de getStreetNameFromLatLong(lat, lng) op die aan Google een request doet.
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <returns>Een straat naam of null als er een query limiet is.</returns>
        private string getStreetName(decimal lat, decimal lng)
        {
            string streetname = string.Empty;
            GeoData gd = _geoDataRepository.findStraatViaLatLng(lat, lng);

            if (gd == null)
            {
                // bestaat niet dus google opvragen en inserten in db.
                GeoCodingWrapper googleData = getStreetNameFromLatLong(lat, lng);
                if (googleData != null)
                {
                    streetname = googleData.address_components.ElementAt(1).long_name;

                    GeoData geo = new GeoData();
                    geo.straatNaam = googleData.address_components.ElementAt(1).long_name;
                    geo.lat = lat;
                    geo.lng = lng;

                    _geoDataRepository.Add(geo);
                }
                else
                {
                    // Er is een over query_limiet gereturned waardoor we geen data hebben. Daarom stoppen we de forloop en beginnen we opnieuw met het aanvragen.
                    // Totdat alle combinaties in de DB staan en we niks naar google hoeven te doen.
                    return null;
                }
            }
            else
            {
                streetname = gd.straatNaam;
            }
            return streetname;
        }

        /// <summary>
        /// Berekent aan de hand van een RouteWrapper model metadata over de route. Het toont alle soorten bestratingen in de route en de gemiddelde moeilijkheidsgraad.
        /// </summary>
        /// <param name="routewrapper"></param>
        /// <returns>Een lijst van CompleteDirectionsWrapper models waarin alle route + metadata staat.</returns>
        private List<CompleteDirectionsWrapper> getCompleteDirectionsModel(RouteWrapper routewrapper)
        {
            List<CompleteDirectionsWrapper> cdw = new List<CompleteDirectionsWrapper>();
            int idCount = 1;

            foreach (routes route in routewrapper.routes)
            {
                bool validRoute = true;

                CompleteDirectionsWrapper com = new CompleteDirectionsWrapper();

                int sum = 0;
                int count = 0;

                foreach (legs leg in route.legs)
                {
                    foreach (steps step in leg.steps)
                    {
                        string streetname = getStreetName(step.start_location.lat, step.start_location.lng);
                        if (streetname == null) { return null; }

                        List<Bestrating> bestrating = _bestratingRepository.FindBestratingByStreetName(streetname);

                        if (bestrating == null || bestrating.Count() <= 0)
                        {
                            com.setStraatZonderBestratingGegevensInDBVoorgekomen(true);
                        }
                        else
                        {
                            int waarde = 0;
                            int aantalWaarde = 0;

                            foreach (Bestrating item in bestrating)
                            {
                                waarde += item.Waarde;
                                aantalWaarde++;
                            }

                            waarde /= (aantalWaarde == 0 ? 1 : aantalWaarde);

                            sum += waarde;
                            count++;

                            foreach (Bestrating item in bestrating)
                            {
                                if (!com.getSoortenBestratingInRoute().Contains(item.Materiaal))
                                {
                                    com.addSoortenBestratingRoute(item.Materiaal);
                                }
                            }
                        }
                    }
                    if (!validRoute)
                    {
                        break;
                    }
                }
                if (validRoute && count != 0)
                {
                    com.gemiddeldeBestratingWaarde = sum / count;
                    com.route = route;
                    com.id = idCount;
                    idCount++;
                    cdw.Add(com);
                }
            }
            return cdw;
        }

        /// <summary>
        /// Geen een RouteWrapper terug waarin alle route gegevens vermeld staan. Hiervoor wordt gebruik gemaakt van de Google Directions API.
        /// </summary>
        /// <param name="FromAddress">Het begin adres</param>
        /// <param name="ToAddress">Het eind adres</param>
        /// <returns>RouteWrapper model gevuld met alle gegevens die in de route bevinden.</returns>
        private RouteWrapper getRouteDirectionsModel(String FromAddress, String ToAddress)
        {
            String[] addresses = { FromAddress, ToAddress };

            string address = String.Format("https://maps.googleapis.com/maps/api/directions/json?origin={0}&destination={1}&mode=walking&alternatives=true&language=nl&units=metric&avoid=tolls|highways&sensor=false", addresses);
            string result = new System.Net.WebClient().DownloadString(address);

            JObject jobj = JObject.Parse(result);
            IList<JToken> resultsRoutes = jobj["routes"].Children().ToList();

            RouteWrapper rw = new RouteWrapper();
            rw.status = jobj["status"].ToString();

            List<routes> DirectionResponse = new List<routes>();

            foreach (JToken item in resultsRoutes)
            {
                routes route = JsonConvert.DeserializeObject<routes>(item.ToString());
                DirectionResponse.Add(route);
            }

            rw.routes = DirectionResponse;
            return rw;
        }

        /// <summary>
        /// Geeft de dichtsbijzijnde straat voor de ingevoerde lat en lng. Hierbij wordt gebruikt gemaakt van de Google GeoCoding API.
        /// Als Google het niet wilt berekenen omdat er teveel requests achter elkaar worden uitgevoerd dan wordt null gereturned.
        /// </summary>
        /// <param name="lat"></param>
        /// <param name="lng"></param>
        /// <returns>GeoCodingWrapper model met daarin alle gegevens. Returned null als Google geen resultaten terug geeft.</returns>
        private GeoCodingWrapper getStreetNameFromLatLong(decimal lat, decimal lng)
        {
            string latt = lat.ToString();
            string lngg = lng.ToString();

            latt = latt.Replace(',', '.');
            lngg = lngg.Replace(',', '.');

            string[] latlng = { latt, lngg };

            string address = String.Format("https://maps.googleapis.com/maps/api/geocode/json?latlng={0},{1}&language=nl&sensor=false", latlng);
            string result = new System.Net.WebClient().DownloadString(address);

            JObject jobj = JObject.Parse(result);


            GeoCodingWrapper gw = new GeoCodingWrapper();

            int count = jobj["results"].Count(); 

            if (count <= 0)
            {
                // Geen waarde. Opnieuw uitrekenen.
                return null;
            }
            else 
            {
                IList<JToken> resultsGeoCoding = jobj["results"][0]["address_components"].Children().ToList();

                gw.status = jobj["status"].ToString();

                List<long_short_types> list = new List<long_short_types>();

                foreach (JToken item in resultsGeoCoding)
                {
                    long_short_types obj = JsonConvert.DeserializeObject<long_short_types>(item.ToString());
                    list.Add(obj);
                }

                gw.address_components = list;
            }
            return gw;
        }

    }
}
